package com.franklin.java.service.database;

import com.flychord.jdk.util.matchcase.TupleCase;
import com.flychord.jdk.util.tuple.Tuple2;
import com.franklin.java.configuration.sql.completion.TableNamePrefixState;
import com.franklin.java.configuration.sql.type.TypeMappingState;
import com.franklin.java.entity.sql.ColumnInfo;
import com.franklin.java.utils.EntityClassHelper;
import com.franklin.java.utils.PsiUtils;
import com.franklin.java.utils.SqlHelper;
import com.franklin.plugins.common.utils.DataCheckUtils;
import com.franklin.plugins.common.utils.StringHelper;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.flychord.jdk.util.tuple.TupleAble.tuple;

/**
 * 数据库处理器抽象父类
 * @author Franklin
 * @since 2021/9/3 11:42
 */
public abstract class AbstractDatabaseHandler implements DatabaseHandler {
    protected Project myProject;

    protected PsiElement psiElement;

    protected AbstractDatabaseHandler(PsiElement psiElement) {
        this.myProject = psiElement.getProject();
        this.psiElement = psiElement;
    }

    /**
     * 返回空处理器，防止空指针
     */
    public static class EmptyDatabaseHandler extends AbstractDatabaseHandler {

        private final static EmptyDatabaseHandler INSTANCE = new EmptyDatabaseHandler(null);

        private EmptyDatabaseHandler(PsiElement psiElement) {
            super(psiElement);
        }

        @Override
        public void resolveColumnInfo(List<ColumnInfo> columnInfoList) {
            //do nothing
        }

        @Override
        public Map<String, List<ColumnInfo>> getColumns(Collection<String> tableNames) {
            return Collections.emptyMap();
        }


        public static EmptyDatabaseHandler getInstance() {
            return INSTANCE;
        }
    }

    /**
     * 实体类数据库处理器
     * 用来处理未连接数据库，或无法从数据库中解析字段信息
     * 从实体类中解析
     * @author Franklin
     * @since 2021/9/3 11:42
     */
    public static class EntityDatabaseHandler extends AbstractDatabaseHandler {

        public EntityDatabaseHandler(PsiElement psiElement) {
            super(psiElement);
        }

        @Override
        public void resolveColumnInfo(List<ColumnInfo> columnInfoList) {
            if (DataCheckUtils.isEmpty(columnInfoList)) {
                return;
            }
            String prefixListStr = TableNamePrefixState.getPrefix(myProject);
            String[] prefixes = prefixListStr.split(";");

            String path = psiElement.getContainingFile().getVirtualFile().getPath();
            Map<String, Map<String, ColumnInfo>> tableNameColumnInfoMapMap = columnInfoList.stream()
                .map(ColumnInfo::getTableName)
                .filter(tableName -> !tableName.equals(SqlHelper.UNKNOWN_TABLE))
                .distinct()
                .map(tableName -> {
                    String entityName = resolveEntityName(prefixes, tableName);
                    return tuple(entityName, tableName);
                })
                .map(
                    TupleCase.match(
                        (entityName, tableName) -> {
                            PsiClass entityClass = PsiUtils.findClassBySimpleName(myProject, path, entityName);
                            return tuple(tableName, entityClass);
                        }
                    )
                )
                .filter(tuple -> Objects.nonNull(tuple._2()))
                .flatMap(tuple -> EntityClassHelper.resolveColumnInEntity(tuple._2(), tuple._1()).stream())
                .collect(Collectors.groupingBy(
                    ColumnInfo::getTableName,
                    Collectors.toMap(ColumnInfo::getColumnName, Function.identity())
                ));

            doFillColumnInfo(columnInfoList, tableNameColumnInfoMapMap);
        }

        @NotNull
        public static String resolveEntityName(String[] prefixes, String tableName) {
            String entityName = tableName;
            for (String prefix : prefixes) {
                if (tableName.startsWith(prefix) && tableName.length() > prefix.length()) {
                    entityName = tableName.substring(prefix.length());
                    if (entityName.charAt(0) == '-') {
                        entityName = entityName.substring(1);
                    }
                    break;
                }
            }
            entityName = StringHelper.getEntityClassName(entityName);
            return entityName;
        }

        @Override
        public Map<String, List<ColumnInfo>> getColumns(Collection<String> tableNames) {
            String path = psiElement.getContainingFile().getVirtualFile().getPath();
            String prefixListStr = TableNamePrefixState.getPrefix(myProject);
            String[] prefixes = prefixListStr.split(";");
            return tableNames.stream()
                .distinct()
                .map(tableName -> {
                    String entityName = resolveEntityName(prefixes, tableName);
                    return tuple(tableName,PsiUtils.findClassBySimpleName(myProject, path, entityName));
                })
                .filter(tuple -> Objects.nonNull(tuple._2()))
                .map(
                    TupleCase.match(
                        (tableName,entityClass) -> {
                            return tuple(tableName,EntityClassHelper.resolveColumnInEntity(entityClass,tableName));
                        }
                    )
                )
                .collect(Collectors.toMap(Tuple2::_1,Tuple2::_2));
        }

        private void doFillColumnInfo(List<ColumnInfo> columnInfoList, Map<String, Map<String, ColumnInfo>> tableNameColumnInfoMapMap) {
            if (DataCheckUtils.isEmpty(tableNameColumnInfoMapMap)) {
                return;
            }

            Map<String, String> reverseMapping = TypeMappingState.getReverseMapping(myProject);

            columnInfoList.forEach(columnInfo -> {
                Map<String, ColumnInfo> columnNameColumnInfoMap = tableNameColumnInfoMapMap
                    .getOrDefault(columnInfo.getTableName(), Collections.emptyMap());

                Optional.ofNullable(columnNameColumnInfoMap.get(columnInfo.getColumnName()))
                    .ifPresent(columnInfoFromEntity -> {
                        columnInfo.setJavaType(columnInfoFromEntity.getJavaType());
                        columnInfo.setColumnType(reverseMapping.get(columnInfoFromEntity.getJavaType()));
                        String comment = columnInfoFromEntity.getComment();
                        if (DataCheckUtils.isNotEmpty(comment)){
                            comment = "entity:" + comment;
                        }
                        columnInfo.setComment(comment);
                        columnInfo.setTableComment(columnInfoFromEntity.getTableComment());
                    });
            });
        }
    }
}
